package cn.denvie.agent.common;

import cn.denvie.agent.collector.ClassCollector;
import cn.denvie.agent.collector.ICollector;
import cn.denvie.agent.collector.JdbcCollector;
import cn.denvie.agent.configsource.AgentArgumentConfigurationSource;
import cn.denvie.agent.configsource.PropertyFileConfigurationSource;
import cn.denvie.agent.configsource.SystemPropertyConfigurationSource;
import cn.denvie.agent.filter.IFilter;
import cn.denvie.agent.filter.JSONFilter;
import cn.denvie.agent.logger.FileLogger;
import cn.denvie.agent.logger.ILogger;
import cn.denvie.agent.logger.JclLogger;
import cn.denvie.agent.utils.CpuUtils;
import cn.denvie.agent.utils.StringUtils;
import org.stagemonitor.configuration.source.ConfigurationSource;

import java.lang.instrument.Instrumentation;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Agent上下文。
 *
 * @author DengZhaoyong
 * @version 1.0.0
 */
public class AgentContext {

    // 线程池
    private static ThreadPoolExecutor threadPool;
    private static ILogger jclLogger;

    private Instrumentation instrumentation;
    private List<ConfigurationSource> configurationSources = new ArrayList<>();
    private List<ICollector> collectors = new ArrayList();
    private IFilter filter;
    private ILogger fileLogger;

    static {
        threadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(CpuUtils.workerCount());
        threadPool.setKeepAliveTime(5L, TimeUnit.MILLISECONDS);
        threadPool.allowCoreThreadTimeOut(true);

        jclLogger = new JclLogger(AgentContext.class.getName());
    }

    private AgentContext() {
    }

    public static AgentContext init(String argument, Instrumentation instrumentation) {
        jclLogger.info("AgentContext init start.");

        // 初始化实例
        AgentContext agentContext = new AgentContext();

        agentContext.instrumentation = instrumentation;

        // 装载系统属性配置
        agentContext.configurationSources.add(new SystemPropertyConfigurationSource());
        // 装载代理属性配置
        if (!StringUtils.isEmpty(argument)) {
            agentContext.configurationSources.add(AgentArgumentConfigurationSource.parse(argument));
        }
        // 装载classpath下的agent.properties属性配置
        if (PropertyFileConfigurationSource.isPresent(AgentConfig.AGENT_PROPERTIES_FILE_NAME)) {
            agentContext.configurationSources.add(new PropertyFileConfigurationSource(AgentConfig.AGENT_PROPERTIES_FILE_NAME));
        }

        // 注册Filter
        agentContext.filter = new JSONFilter();

        // 注册Logger
        String rootDir = agentContext.getConfig(AgentConfig.LOG_ROOT_DIR, AgentConfig.DEFAULT_LOG_ROOT_DIR);
        String logPath = agentContext.getConfig(AgentConfig.LOG_PATH_PATTERN, AgentConfig.DEFAULT_LOG_PATH_PATTERN);
        String fileName = agentContext.getConfig(AgentConfig.LOG_FILE_NAME, AgentConfig.DEFAULT_LOG_FILE_NAME);
        agentContext.fileLogger = new FileLogger()
                .setRootDir(rootDir)
                .setDirPattern(logPath)
                .setFileName(fileName);

        // 注册采集器
        agentContext.collectors.add(new ClassCollector(agentContext, instrumentation));
        agentContext.collectors.add(new JdbcCollector(agentContext, instrumentation));

        jclLogger.info("AgentContext init end.");
        return agentContext;
    }

    // 递交采集结果
    public void submitCollectResult(final Object value) {
        // 后台线程执行日志输出
        if (value == null) return;

        final Object result = filter.doFilter(value);
        threadPool.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("ThreadName: " + Thread.currentThread().getName());
                jclLogger.info("Write Agent Log: " + result);
                fileLogger.info(result);
            }
        });
    }

    /**
     * 获取配置，顺序：系统属性 => 代理属性 => 配置文件。
     *
     * @param key          配置名，参考{@link AgentConfig}
     * @param defaultValue 默认值
     * @return
     */
    public String getConfig(String key, String defaultValue) {
        for (ConfigurationSource configSource : configurationSources) {
            if (!StringUtils.isEmpty(configSource.getValue(key))) {
                return configSource.getValue(key);
            }
        }
        return defaultValue;
    }

    public Instrumentation getInstrumentation() {
        return instrumentation;
    }

    public List<ICollector> getCollectors() {
        return collectors;
    }

}
